/*
 * File     : PrimitiveStructureField.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.googlecode.dni.type.nativetype.NativeChar;
import com.googlecode.dni.type.nativetype.NativeInt;
import com.googlecode.dni.type.nativetype.NativeLong;
import com.googlecode.dni.type.nativetype.NativeShort;
import com.googlecode.dni.type.nativetype.SizeT;
import com.googlecode.dni.type.nativetype.Unsigned;
import com.googlecode.dni.type.nativetype.WCharT;
import com.googlecode.dni.type.structure.IllegalStructureException;


/**
 * Encapsulates details of a primitive structure's field used during class
 * generation.
 *
 * @author Matthew Wilson
 */
final class PrimitiveStructureField extends StructureField
{

    private Class< ? > type;

    private PrimitiveType primitiveType;

    private boolean unsigned;


    /**
     * {@inheritDoc}
     */
    @Override
    void setGetter( final Method getter )
    {
        super.setGetter( getter );

        setNativeAnnotation( getter, NativeChar.class,  int.class );
        setNativeAnnotation( getter, NativeShort.class, int.class );
        setNativeAnnotation( getter, NativeInt.class,   int.class );
        setNativeAnnotation( getter, NativeLong.class,  long.class );
        setNativeAnnotation( getter, WCharT.class,      int.class );
        setNativeAnnotation( getter, SizeT.class,       long.class );

        // wchar_t and sizr_t have to be unsigned; others are optional
        this.unsigned = ( this.type == WCharT.class || this.type == SizeT.class )
                        || getter.getAnnotation( Unsigned.class ) != null;

        if ( this.unsigned
             && this.type != NativeChar.class
             && this.type != NativeShort.class
             && this.type != WCharT.class
             && this.type != SizeT.class )
        {
            throw new IllegalStructureException( "Meaningless @Unsigned annotation: " + getter );
        }

        if ( this.type == null )
        {
            this.type = getter.getReturnType();
        }

        this.primitiveType = PrimitiveType.getType( this.type );
    }

    @Override
    String getCType()
    {
        return this.primitiveType.getCType();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getSize()
    {
        return this.primitiveType.getSize();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getPaddingUnitSize()
    {
        return getSize();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        writeGetPointer( methodVisitor, implName );

        this.primitiveType.writeGet( methodVisitor, this.unsigned );

        methodVisitor.visitInsn( this.primitiveType.getReturnOpcode() );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeSetter( final MethodVisitor methodVisitor,
                      final String implName )
    {
        writeGetPointer( methodVisitor, implName );

        if ( this.type == float.class )
        {
            methodVisitor.visitVarInsn( Opcodes.FLOAD, 1 );
        }
        else if ( this.type == double.class )
        {
            methodVisitor.visitVarInsn( Opcodes.DLOAD, 1 );
        }
        else if ( this.primitiveType.getJavaSize() < 8 )
        {
            methodVisitor.visitVarInsn( Opcodes.ILOAD, 1 );
        }
        else
        {
            methodVisitor.visitVarInsn( Opcodes.LLOAD, 1 );
        }
        this.primitiveType.writeSet( methodVisitor );

        methodVisitor.visitInsn( Opcodes.RETURN );
    }

    /**
     * Checks the getter for a <code>Native<i>Xxx</i></code> annotation, and
     * also checks the return type matches.
     *
     * @param getter
     *            the getter
     * @param annotationType
     *            the annotation type
     * @param javaType
     *            the expected Java primitive type
     */
    private void setNativeAnnotation( final Method getter,
                                      final Class< ? extends Annotation > annotationType,
                                      final Class< ? > javaType )
    {
        Annotation annotation = getter.getAnnotation( annotationType );

        if ( annotation != null )
        {
            if ( this.type != null )
            {
                throw new IllegalArgumentException( "Multiple NativeXxx annotations not permitted: " + getter );
            }

            if ( getter.getReturnType() != javaType )
            {
                throw new IllegalArgumentException( annotationType.getSimpleName() + " requires Java type of " + javaType.getName()
                                                    + ": " + getter );
            }

            this.type = annotationType;
        }
    }

}
